import errno
import logging
import os
import platform
import select
import socket
import sys
import time

log = logging.getLogger('ioloop')
OSNAME = platform.system()

if OSNAME == 'Linux' or OSNAME == 'Darwin':
    import fcntl
    
# -----------------------------------------------------------------------------
class Handle:
    def read_ready(self):  return False
    def write_ready(self): return False
    
    def close(self): pass
    def is_closed(self): return False
    
    def handle_read(self):  pass
    def handle_write(self): pass
    def handle_error(self): pass

    def fileno(self):
        raise NotImplementedError()

# -----------------------------------------------------------------------------
class ListenFactory:
    def make_handle(self, conn):
        raise NotImplementedError()

# -----------------------------------------------------------------------------
class ListenHandle(Handle):
    def __init__(self, ap, factory, max_waiting = 5):
        self.ap_ = ap
        self.max_waiting_ = max_waiting
        self.factory_ = factory
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind(self.ap_)
        sock.listen(self.max_waiting_)
        log.debug('created listening sock %s:%s' % ap)
        self.sock_ = sock
        self.closed_ = False

    def __repr__(self):
        return '<ioloop.ListenHandle %s:%s>' % self.ap_
    
    def read_ready(self):
        return True

    def close(self):
        log.debug('%s closed' % self)
        self.sock_.close()
        self.closed_ = True

    def is_closed(self):
        return self.closed_

    def handle_read(self):
        conn, ap = self.sock_.accept()
        log.debug('accepted connection %s from %s' % (conn, str(ap)))
        s = self.factory_.make_handle(conn)
        master_loop.add_handle(s)

    def handle_error(self):
        raise Exception('listening socket %s had an error' % self)

    def fileno(self):
        return self.sock_.fileno()

# -----------------------------------------------------------------------------
class UnixListenHandle(ListenHandle):
    """This creates a UNIX domain socket to listen to."""
    
    def __init__(self, sock_file, factory, max_waiting = 5):
        self.sock_file_ = sock_file
        self.max_waiting_ = max_waiting
        self.factory_ = factory
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

        if os.path.exists(self.sock_file_):
            try:
                log.info('found stale domain sock %s, removing' %
                         self.sock_file_)
                os.remove(self.sock_file_)
            except OSError:
                log.error("couldn't remove sock %s, exiting..." %
                          self.sock_file_)
                sys.exit(1)
        
        sock.bind(self.sock_file_)
        sock.listen(self.max_waiting_)
        log.debug('created listening sock at %s' % self.sock_file_)
        self.sock_ = sock
        self.closed_ = False

    def __repr__(self):
        return '<ioloop.UnixListenHandle %s>' % self.sock_file_

    def __del__(self):
        self.close()
        os.remove(self.sock_file_)

# -----------------------------------------------------------------------------
class SockHandle(Handle):
    def __init__(self, sock, max_read_bytes = 1024):
        self.sock_ = sock
        self.read_buf_  = ''
        self.write_buf_ = ''
        self.write_done_cbs_ = []
        self.closed_ = False
        self.max_read_bytes = max_read_bytes

    def __repr__(self):
        try:
            fd = self.sock_.fileno()
        except:
            fd = 'X'
        return '<ioloop.SockHandle %s>' % fd

    def set_blocking(self, b):
        self.sock_.setblocking(b)
                
    def read_ready(self):
        return True

    def write_ready(self):
        return self.write_buf_ != ''

    def is_closed(self):
        return self.closed_
    
    def fileno(self):
        return self.sock_.fileno()

    def parse_read_buf(self):
        idx = self.read_buf_.find('\n')
        if idx == -1:
            return False
        msg = self.read_buf_[:idx]
        self.recv_frame(msg)
        # +1 is to skip the \n
        self.read_buf_ = self.read_buf_[idx + 1:]
        return True

    def send_frame(self, msg):
        return self.send_bytes(msg + '\n')

    def recv_frame(self, msg):
        """Override this in subclasses."""
        raise NotImplementedError()
    
    def handle_read(self):
        try:
            b = self.sock_.recv(self.max_read_bytes)
            if b == '':
                log.debug('close from other side, closing')
                self.close()
                return
            log.debug('got %d bytes: "%s"' % (len(b), b[:100].strip()))
        except Exception, e:
            # XXX/bowei ?? what are the possible errors here?
            log.warn('exception occurred in recv: %s' % e)
            self.close()
	    return
        self.read_buf_ = self.read_buf_ + b
        while self.parse_read_buf(): pass

    def handle_write(self):
        # log.debug('writing to socket %s' % self)
        try:
            cc = self.sock_.send(self.write_buf_)
            # log.debug('sent %d bytes' % cc)
        except socket.error, e:
            if e.args[0] == errno.EAGAIN:
                log.debug('kernel socket buffer for %s is full' % self)
                return
            else:
                # XXX/bowei ?? what are the possible errors here?
                log.warn('exception occurred in write: %s' % e)
                self.close()
		return
        self.write_buf_ = self.write_buf_[cc:]
        self.handle_cbs(cc)

    def handle_cbs(self, cc):
        while cc > 0 and len(self.write_done_cbs_) > 0:
            next_cc, cb = self.write_done_cbs_[0]
            if cc < next_cc:
                self.write_done_cbs_[0][0] = next_cc - cc
                break
            else:
                cc = cc - next_cc
                self.write_done_cbs_ = self.write_done_cbs_[1:]
                if cb != None: cb()

    def send_bytes(self, bytes, write_done_cb = None):
        # log.debug('send_bytes("%s")' % bytes)
        was_empty = (self.write_buf_ == '')
        self.write_buf_ = self.write_buf_ + bytes
        # XXX/bowei -- don't append a callback if there is none
        self.write_done_cbs_.append((len(bytes), write_done_cb))
        if was_empty:
            self.handle_write()

    def handle_error(self):
        self.close()

    def close(self):
        if not self.closed_:
            self.sock_.close()
        self.closed_ = True

# -----------------------------------------------------------------------------
class StdinSockAdapter:
    """Adapts stdin to be used as a socket (for testing purposes) in
    place of an ordinary Python socket in the Handles."""
    
    def __init__(self):
        self.closed_ = False

    def __repr__(self):
        return '<StdinSockAdapter %s>' % (sys.stdin)

    def fileno(self):
        return sys.stdin.fileno()

    def recv(self, nbytes):
        if self.closed_:
            return ''
        return sys.stdin.readline()
        # XXX/bowei -- should switch over to real asynch
        # b = sys.stdin.read(nbytes)
        # return b
        
    def send(self, bytes):
        if self.closed_:
            return
        log.info('StdinSockAdapter: "%s"' % bytes)
        return len(bytes)

    def close(self):
        self.closed_ = True
    
# -----------------------------------------------------------------------------
class FileToSockAdapter:
    """Adapts an open file to be used by the SockAdapter (warning: may
    not work on all systems, namely Windows."""
    
    def __init__(self, f):
        self.f_ = f
        
    def setblocking(self, b):
        fd = self.f_.fileno()
        flags = fcntl.fcntl(fd, fcntl.F_GETFL)
        if b:
            flags = flags & (~os.O_NONBLOCK)
        else:
            flags = flags | os.O_NONBLOCK            
        fcntl.fcntl(self.f_.fileno(), fcntl.F_SETFL, flags)
        
    def recv(self, nbytes):
        return self.f_.read(nbytes)

    def send(self, bytes):
        return self.f_.write(bytes)

    def fileno(self):
        return self.f_.fileno()

    def close(self):
        self.f_.close()

    def file(self):
        return self.f_
    
# -----------------------------------------------------------------------------
class Alarm:
    def __init__(self, timeout, cb):
        self.timeout_ = timeout + time.time()
        self.cb_ = cb

    def __repr__(self):
        return '<ioloop.Alarm %s>' % self.timeout_

    def timeout(self):
        self.cb_()

# -----------------------------------------------------------------------------
class SelectLoop:
    def __init__(self):
        self.handles_ = []
        self.alarms_  = []
        self.exit_cb_ = None

    def set_exit_cb(self, cb):
        self.exit_cb = cb

    def set_alarm(self, alarm):
        for i in xrange(len(self.alarms_)):
            if alarm.timeout_ < self.alarms_[i].timeout_:
                self.alarms_.insert(i, alarm)
                return
        self.alarms_.append(alarm)

    def fire_alarms(self):
        t = time.time()
        assert(len(self.alarms_) < 4)
        while len(self.alarms_) > 0:
            if self.alarms_[0].timeout_ <= t:
                alarm = self.alarms_[0]
                self.alarms_ = self.alarms_[1:]
                alarm.timeout()
            else:
                break
                
    def next_timeout(self):
        if len(self.alarms_) == 0:
            return None
        t = self.alarms_[0].timeout_ - time.time()
        if t < 0:
            t = 0
        return t

    def add_handle(self, h):
        log.debug('add handle %s' % h)
        self.handles_.append(h)

    def remove_handle(self, h):
        log.debug('remove handle %s' % h)
        try:
            self.handles_.remove(h)
        except ValueError:
            log.error('removing non-existant handle %s' % h)

    def read_handles(self):
        return [h for h in self.handles_ if h.read_ready()]
        
    def write_handles(self):
        return [h for h in self.handles_ if h.write_ready()]
    
    def error_handles(self):
        return self.handles_

    def run(self):
        while True:
            self.handles_ = [h for h in self.handles_ if not h.is_closed()]
            
            r = self.read_handles()
            w = self.write_handles()
            e = self.error_handles()
            timeout = self.next_timeout()
            
            log.debug('select: %d w: %d e: %d and %s timeout' %
                      (len(r), len(w), len(e), timeout))
            # log.debug('select: %s w: %s e: %s and %s timeout' %
            #          (r, w, e, timeout))
	    try:
                if OSNAME == 'Windows' and (len(r) + len(w) + len(e)) == 0:
                    time.sleep(timeout)
                else:
                    r, w, e = select.select(r, w, e, timeout)
	    except KeyboardInterrupt, e:
		# raise e
                log.info('got keyboard interrupt, exiting...')
                if self.exit_cb_ is not None: self.exit_cb_()
                sys.exit(0)
	    except Exception, e:
		log.warn('select bailed because of %s' % e)
                raise ##### XXXXXXX
		continue

            log.debug('select returned r: %d w: %d e: %d' %
                      (len(r), len(w), len(e)))
            for h in e:
                if not h.is_closed(): h.handle_error()
            for h in r:
                if not h.is_closed(): h.handle_read()
            for h in w:
                if not h.is_closed(): h.handle_write()
            self.fire_alarms()

#
# Module global variable for the master server
#
master_loop = SelectLoop()
